package com.bugsnag.ohos.ndk;

import com.bugsnag.ohos.Logger;
import com.bugsnag.ohos.NativeInterface;
import com.bugsnag.ohos.StateEvent;
import com.bugsnag.ohos.StateEvent.AddBreadcrumb;
import com.bugsnag.ohos.StateEvent.AddMetadata;
import com.bugsnag.ohos.StateEvent.ClearMetadataSection;
import com.bugsnag.ohos.StateEvent.ClearMetadataValue;
import com.bugsnag.ohos.StateEvent.Install;
import com.bugsnag.ohos.StateEvent.StartSession;
import com.bugsnag.ohos.StateEvent.UpdateContext;
import com.bugsnag.ohos.StateEvent.UpdateInForeground;
import com.bugsnag.ohos.StateEvent.UpdateUser;
import com.bugsnag.ohos.internal.StateObserver;
import com.bugsnag.ohos.util.OhosLog;

import java.io.File;
import java.nio.charset.Charset;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Observes changes in the Bugsnag environment, propagating them to the native layer
 */
public class NativeBridge implements StateObserver {

    private ReentrantLock lock = new ReentrantLock();
    private AtomicBoolean installed = new AtomicBoolean(false);
    public static String reportDirectory = NativeInterface.getNativeReportPath();
    private static Logger logger = NativeInterface.getLogger();

    private boolean is32bit() {
        String[] abis = NativeInterface.getCpuAbi();
        for (String s : abis) {
            if (s.contains("64")) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void onStateChange(StateEvent event) {
        if (isInvalidMessage(event)) return;
        if (event instanceof Install) {
            handleInstallMessage((Install) event);
            deliverPendingReports();
        } else if (event instanceof AddMetadata) {
            handleAddMetadata((AddMetadata) event);
        } else if (event instanceof ClearMetadataSection) {
            clearMetadataTab(makeSafe(((ClearMetadataSection) event).getSection()));
        } else if (event instanceof ClearMetadataValue) {
            String a = makeSafe(((ClearMetadataValue) event).getSection());
            String b = makeSafe(((ClearMetadataValue) event).getKey() == null ? "" : ((ClearMetadataValue) event).getKey());
            removeMetadata(a, b);
        } else if (event instanceof AddBreadcrumb) {
            addBreadcrumb(makeSafe(((AddBreadcrumb) event).getMessage()), makeSafe(((AddBreadcrumb) event).getType().toString()),
                makeSafe(((AddBreadcrumb) event).getTimestamp()), ((AddBreadcrumb) event).getMetadata());
            addHandledEvent();
            addUnhandledEvent();
            pausedSession();
        } else if (event instanceof StartSession) {
            startedSession(makeSafe(((StartSession) event).getId()), makeSafe(((StartSession) event).getStartedAt()), ((StartSession) event).getHandledCount(),
                ((StartSession) event).getUnhandledCount());
        } else if (event instanceof UpdateContext) {
            updateContext(makeSafe(((UpdateContext) event).getContext() == null ? "" : ((UpdateContext) event).getContext()));
        } else if (event instanceof UpdateInForeground) {
            updateInForeground(((UpdateInForeground) event).isInForeground(), makeSafe(((UpdateInForeground) event).getContextAbility() == null
                ? "" : ((UpdateInForeground) event).getContextAbility()));
        } else if (event instanceof StateEvent.UpdateLastRunInfo) {
            makeSafe(((StateEvent.UpdateLastRunInfo) event).getConsecutiveLaunchCrashes() == 0 ? "0" : String.valueOf(((StateEvent.UpdateLastRunInfo) event).getConsecutiveLaunchCrashes()));
        } else if (event instanceof StateEvent.UpdateIsLaunching) {
            updateIsLaunching(((StateEvent.UpdateIsLaunching) event).isLaunching());
        } else if (event instanceof StateEvent.UpdateOrientation) {
            updateOrientation(((StateEvent.UpdateOrientation) event).getOrientation() == null ? "" : ((StateEvent.UpdateOrientation) event).getOrientation());
        } else if (event instanceof UpdateUser) {
            updateUserId(makeSafe(((UpdateUser) event).getUser().getId() == null ? "" : ((UpdateUser) event).getUser().getId()));
            updateUserName(makeSafe(((UpdateUser) event).getUser().getName() == null ? "" : ((UpdateUser) event).getUser().getName()));
            updateUserEmail(makeSafe(((UpdateUser) event).getUser().getEmail() == null ? "" : ((UpdateUser) event).getUser().getEmail()));
        } else if (event instanceof StateEvent.UpdateMemoryTrimEvent) {
            updateLowMemory(((StateEvent.UpdateMemoryTrimEvent) event).isLowMemory());
        }
    }

    public  native void install(
        String apiKey,
        String reportingDirectory,
        String lastRunInfoPath,
        int consecutiveLaunchCrashes,
        boolean autoDetectNdkCrashes,
        int apiLevel,
        boolean is32bit,
        String appVersion,
        String buildUuid,
        String releaseStage
    );


    public  native void startedSession(
        String sessionID,
        String key,
        int handledCount,
        int unhandledCount
    );

    public  native void deliverReportAtPath(String filePath);

    public  native void addBreadcrumb(String name, String type, String timestamp, Object metadata);

    public  native void addMetadataString(String tab, String key, String value);

    public  native void addMetadataDouble(String tab, String key, Double value);

    public  native void addMetadataBoolean(String tab, String key, Boolean value);

    public  native void addHandledEvent();


    public  native void addUnhandledEvent();

    public  native void clearMetadataTab(String tab);

    public  native void removeMetadata(String tab, String key);

    public  native void pausedSession();

    public  native void updateContext(String context);

    public  native void updateInForeground(Boolean inForeground, String activityName);

    public  native void updateIsLaunching(boolean isLaunching);

    public  native void updateLastRunInfo(int consecutiveLaunchCrashes);

    public  native void updateOrientation(String orientation);

    public  native void updateUserId(String newValue);

    public  native void updateUserEmail(String newValue);

    public  native void updateUserName(String newValue);

    public  native Long getUnwindStackFunction();

    public  native void updateLowMemory(boolean newValue);

    static {
        File outFile = new File(reportDirectory);
        NativeInterface.getLogger();
        if (!outFile.exists() && !outFile.mkdirs()) {
            logger.w("The native reporting directory cannot be created.");
        }
    }

    private boolean isInvalidMessage(Object msg) {
        if (msg == null || !(msg instanceof StateEvent)) {
            return true;
        }
        if (!installed.get() && !(msg instanceof Install)) {
            logger.w("Received message before INSTALL: " + msg);
            return true;
        }
        return false;
    }

    private void deliverPendingReports() {
        OhosLog.d("TAG","deliverPendingReports...");
        lock.lock();
        try {
            File outDir = new File(reportDirectory);
            OhosLog.d("TAG","outDir...%s",outDir.getName());
            OhosLog.d("TAG","outDir.exists()...%s",outDir.exists());
            if (outDir.exists()) {
                File[] fileList = outDir.listFiles();
                if (fileList != null) {
                    OhosLog.d("TAG","fileList.length...%d",fileList.length);
                    for (File file : fileList) {
                        OhosLog.d("NativeBridge","deliverPendingReports() file.getAbsolutePath() ：%s",file.getAbsolutePath());
                        deliverReportAtPath(file.getAbsolutePath());
                    }
                }
            } else {
                logger.w("Payload directory does not exist, cannot read pending reports");
            }
        } catch (Exception ex) {
            logger.w("Failed to parse/write pending reports: " + ex);
        } finally {
            lock.unlock();
        }
    }

    private void handleInstallMessage(Install arg) {
        lock.lock();
        try {
            if (installed.get()) {
                logger.w("Received duplicate setup message with arg: $arg");
            } else {

                OhosLog.d("NativeBridge","reportDirectory： %s",reportDirectory);
                String reportPath = new File(reportDirectory, UUID.randomUUID()+".crash").getAbsolutePath();
                OhosLog.d("NativeBridge Install toString",arg.toString());

                install(
                    makeSafe(arg.getApiKey()),
                    reportPath,
                    makeSafe(arg.getLastRunInfoPath()),
                    arg.getConsecutiveLaunchCrashes(),
                    arg.isAutoDetectNdkCrashes(),
                    /*Build.VERSION.SDK_INT*/0,
                    /* is32bit*/is32bit(),
                    makeSafe(arg.getAppVersion() == null ? "" : arg.getAppVersion()),
                    makeSafe(arg.getBuildUuid() == null ? "" : arg.getBuildUuid()),
                    makeSafe(arg.getReleaseStage() == null ? "" : arg.getReleaseStage())
                );
                installed.set(true);
            }
        } finally {
            lock.unlock();
        }
    }

    private void handleAddMetadata(AddMetadata arg) {
        if (arg.getKey() != null) {
            Object newValue = arg.getValue();
            if (arg.getKey() != null) {
                if (newValue instanceof String) {
                    addMetadataString(arg.getSection(), arg.getKey(), makeSafe((String) newValue));
                } else if (newValue instanceof Boolean) {
                    addMetadataBoolean(arg.getSection(), arg.getKey(), (Boolean) newValue);
                } else if (newValue instanceof Number) {
                    addMetadataDouble(arg.getSection(), arg.getKey(), ((Number) newValue).doubleValue());
                } else {

                }
            }
        }
    }

    /**
     * Ensure the string is safe to be passed to native layer by forcing the encoding
     * to UTF-8.
     */
    private String makeSafe(String text) {
        return new String(text.getBytes(), Charset.defaultCharset());
    }

}
